package TDA_Lista;

import java.util.Iterator;

/**Implementacion del TDA Lista utilizando una estructura simplemente enlazada,
 * en este caso siguiendo los lineamientos de la interfaz PositionList. */
public class SLinkedList<E> implements PositionList<E> {
	private Node<E> head, tail;
	private int size;
	
	/**Constructor de una lista vacia. */
	public SLinkedList(){
		head = null;
		tail = null;
		size = 0;
	}
	
	/**Retorna el numero de elementos en la lista. */
	public int size(){
		return size;
	}
	
	/**Retorna si la lista esta vacia o no. */
	public boolean isEmpty(){
		return (size == 0);
	}
	
	/**Retorna el primer nodo de la lista. */
	public Position<E> first()throws EmptyListException{
		if(isEmpty()) throw new EmptyListException("La lista esta vacia.");
		return head;
	}
	
	/**Retorna el ultimo nodo en la lista. */
	public Position<E> last()throws EmptyListException{
		if(isEmpty()) throw new EmptyListException("La lista esta vacia.");
		return tail;
	}
	
	/**Verifica que la posicion sea valida. */
	private Node<E> checkPosition(Position<E> p)throws InvalidPositionException{
		if(p == null) throw new InvalidPositionException("La posicion es invalida. ");
		try{
			Node<E> aux = (Node<E>) p;
			return aux;
		}
		catch(ClassCastException e){
			throw new InvalidPositionException("Error de casteo. ");
		}
	}
	
	/**Retorna el nodo siguiente a un nodo dado en la lista. */
	public Position<E> next(Position<E> p)throws InvalidPositionException, BoundaryViolationException{
		Node<E> aux = checkPosition(p);
		if(aux == tail) throw new BoundaryViolationException("No hay posicion siguiente. ");
		return aux.getNext();
	}
	
	/**Retorna el nodo anterior a un nodo dado en la lista. */
	public Position<E> prev(Position<E> p)throws InvalidPositionException, BoundaryViolationException{
		Node<E> aux = checkPosition(p);
		if(aux == head) throw new BoundaryViolationException("No hay posicion anterior.");
		Node<E> aux2 = head;
		while((aux2.getNext() != aux)&&(aux2.getNext() != null))
			aux2 = aux2.getNext();
		if(aux2.getNext() == null) throw new InvalidPositionException("La posicion no pertenece a la lista. ");
		return aux2;
	}
	
	/**Inserta un elemento al principio de la lista. */
	public void addFirst(E e){
		Node<E> aux = new Node<E>(e,head);
		head = aux;
		if(isEmpty()) tail = aux;
		size ++;
	}
	
	/**Inserta un elemento al final de la lista. */
	public void addLast(E e){
		Node<E> aux = new Node<E>(e,null);
		if(isEmpty()){
			head = aux;
			tail = aux;
		}
		else{
			tail.setNext(aux);
			tail = aux;
		}
		size ++;
	}
	
	/**Inserta un elemento despues de un nodo dado en la lista. */
	public void addAfter(Position<E> p, E e)throws InvalidPositionException{
		Node<E> aux = checkPosition(p);
		if(aux == tail) addLast(e);
		else{
			Node<E> nuevo = new Node<E>(e,null);
			nuevo.setNext(aux.getNext());
			aux.setNext(nuevo);
			size ++;
		}
	}
	
	/**Inserta un elemento antes de un nodo dado en la lista. */
	public void addBefore(Position<E> p, E e)throws InvalidPositionException{
		Node<E> aux = checkPosition(p);
		if(aux == head) addFirst(e);
		else{
			try {
				Node<E> nuevo = new Node<E>(e,null);
				Node<E> prev = checkPosition(prev(p));
				nuevo.setNext(aux);
				prev.setNext(nuevo);
				size ++;
			} 
			catch (BoundaryViolationException e1){
				throw new InvalidPositionException("");
			}
		}
	}
	
	/**Remueve un nodo de la lista, retornando el elemento almacenado en el. */
	public E remove(Position<E> p)throws InvalidPositionException{
		Node<E> aux = checkPosition(p);
		E elem = aux.element();
		//Si la lista tiene un solo nodo
		if((aux == head)&&(aux.getNext() == null)){
			head = null;
			tail = null;
			size --;
		}
		else{
			//Si el nodo a remover es la cabeza
			if(aux == head){
				aux = head.getNext();
				head.setNext(null);
				head = aux;
				size --;
			}
			else{
				//Si el nodo a remover es la cola
				if(aux == tail){
					try {
						Node<E> aux2 = checkPosition(prev(p));
						aux2.setNext(null);
						tail = aux2;
						size --;
					} 
					catch (BoundaryViolationException e) {
					
					}	
				}
				else{
					//Caso contrario
					if((aux != head)&&(aux != tail)){
						try {
							Node<E> aux2 = checkPosition(prev(p));
							aux2.setNext(aux.getNext());
							aux.setNext(null);
							size --;
						} 
						catch (BoundaryViolationException e) {
					
						}
					}
				}
			}
		}	
		return elem;
	}
	
	/**Reemplaza el elemento almacenado en un nodo dado, retornando el elemento viejo. */
	public E set(Position<E> p, E e)throws InvalidPositionException{
		Node<E> aux = checkPosition(p);
		E elem = aux.element();
		aux.setElement(e);
		return elem;
	}
	
	/** Retorna un iterador para recorrer los elementos de la lista. */
	public Iterator<E> iterator() {
		return new ElementIterator<E>(this);
	}
	
	/**Retorna una lista con las posiciones de los elementos en la lista. */
	public Iterable<Position<E>> positions(){
		SLinkedList<Position<E>> lista = new SLinkedList<Position<E>>();
		if(!isEmpty()){
			try{
				Position<E> pos = first();
				while(pos != last()){
					lista.addLast(pos);
					pos = next(pos);
				}
				lista.addLast(pos);
			}
			catch(EmptyListException ex){}
			catch(InvalidPositionException ex){}
			catch(BoundaryViolationException ex){}
		}
		return lista;
	}

}
